'''
This file is a part of nets7r project
Licensed under New BSD license
H3n0xek (C) 2011 
'''
from components import StateObject, IdentifiedObject
from utils.addr import generate_random_mac, check_mac, \
                        BROADCAST_MAC
from excepts import PortDoesNotExist, MACInvalid, \
                    PortNotMine


class Port(StateObject, IdentifiedObject):
    """Port has mac address, state and associated with a device"""
    
    _device_id = None
    _mac = None
    
    class Meta:
        pass
    
    def __init__(self, device_id):
        super(Port, self).__init__()
        self._device_id = device_id
        self._mac = generate_random_mac()
        
    def __eq__(self, instance):
        same_device = self._device_id == instance._device_id
        same_mac = self._mac == instance._mac
        same_id = self.ID == instance.ID
        return same_id and same_device and same_mac
       
    def _get_mac(self):
        return self._mac
    
    def _set_mac(self, value):        
        if not check_mac(value):
            raise MACInvalid(value)
        self._mac = value
    mac = property(_get_mac, _set_mac)

    def _get_device_id(self):
        return self._device_id
    device_id = property(_get_device_id)
    


class PortCollection(object):
    """Port collection is an object which
    has list of ports and port-macs table
    
    PortCollection may be used when it's need to determine
    a port in which receiver is obtained"""
    
    _device_id = None
    _ports = []
    _table = {}
    
    def get_port(self, port_id):
        """Return Port by port id"""
        for port in self._ports:
            if port.ID == port_id:
                return port
        raise PortDoesNotExist(self._device_id, port_id)
    
    
    def add_port(self, port):
        """Add existent port with correct device_id"""
        if self._device_id != port.device_id:
            raise PortNotMine(self._device_id, port)
        
        if port.__class__ != Port:
            raise ValueError('Expected Port, got %s' \
                             % port.__class__)
            
        if port not in self._ports:
            self._ports.append(port)
    
        
    def create_port(self):
        """Create new port and assign it to this device"""
        p = Port(self._device_id)
        self._ports.append(p)
        self._table[p] = []
        return p

    
    def remove_port(self, port=None, port_id=None):
        """Remove port from ports list and clean table records
        associated with it"""
        if not port and not port_id:
            raise ValueError("You must specify %s or %s in keyword"
                             "args" % ('port', 'port_id'))
        if port_id and not port:
            port = self.get_port(port_id)
        self._table.pop(port)
        self._ports.remove(port)

    
    def _get_ports(self):
        return self._ports
    ports = property(_get_ports)

    
    def update_table(self, port, destination):
        """Call this method with device port and remote
        destination to remember them in port-macs table
        
        device ports matching destination could be resolved later
        by using get_receivers method

        Arguments:

           port - Port instance. port.device_id must be relevant to this
        port collection

           destination - mac address of remote target which could be
        accessible via this port."""
        try:            
            self._ports.index(port)
            port_id = str(getattr(port, 'ID', '?undefined?'))
            if not self._table.has_key(port):
                raise PortDoesNotExist(self._device_id, port_id)
        except (ValueError, KeyError):            
            raise PortDoesNotExist(self._device_id, port_id)
        
        if not check_mac(destination):
            raise MACInvalid(destination)
        
        list_of_macs = self._table[port] 
        if destination not in list_of_macs:
            list_of_macs.append(destination)
            
            
    def get_receivers(self, destination):
        """Return a list of all receiver ports to which
        message must be delivered.
        
        Actually it must return one-element list when we have only one
        port, many ports if packet is a multicast and full ports list
        when packet is a broadcast"""
        if not check_mac(destination):
            raise MACInvalid(destination)
                
        if destination != BROADCAST_MAC:
            receivers = []
            for port in self._table:
                if destination in self._table[port]:
                    receivers.append(destination)
            if len(receivers) > 0:
                return receivers
            destination = BROADCAST_MAC
        
        # broadcast
        return self._ports 
    
    
